Passed
Branch development (e0e718)
by Nils
04:45
created

Aes.Ctr.encrypt   F

Complexity

Conditions 16
Paths 8910

Size

Total Lines 69

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 16
nc 8910
nop 3
dl 0
loc 69
rs 2.5509
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like Aes.Ctr.encrypt often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
2
/*  AES counter-mode (CTR) implementation in JavaScript               (c) Chris Veness 2005-2016  */
3
/*                                                                                   MIT Licence  */
4
/* www.movable-type.co.uk/scripts/aes.html                                                        */
5
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
6
7
/* global WorkerGlobalScope */
8
'use strict';
9
if (typeof module!='undefined' && module.exports) var Aes = require('./aes.js'); // ≡ import Aes from 'aes.js'
10
11
12
/**
13
 * Aes.Ctr: Counter-mode (CTR) wrapper for AES.
14
 *
15
 * This encrypts a Unicode string to produces a base64 ciphertext using 128/192/256-bit AES,
16
 * and the converse to decrypt an encrypted ciphertext.
17
 *
18
 * See http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
19
 *
20
 * @augments Aes
21
 */
22
Aes.Ctr = {};
23
24
25
/**
26
 * Encrypt a text using AES encryption in Counter mode of operation.
27
 *
28
 * Unicode multi-byte character safe
29
 *
30
 * @param   {string} plaintext - Source text to be encrypted.
31
 * @param   {string} password - The password to use to generate a key for encryption.
32
 * @param   {number} nBits - Number of bits to be used in the key; 128 / 192 / 256.
33
 * @returns {string} Encrypted text.
34
 *
35
 * @example
36
 *   var encr = Aes.Ctr.encrypt('big secret', 'pāşšŵōřđ', 256); // 'lwGl66VVwVObKIr6of8HVqJr'
37
 */
38
Aes.Ctr.encrypt = function(plaintext, password, nBits) {
39
    var blockSize = 16;  // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
40
    if (!(nBits==128 || nBits==192 || nBits==256)) throw new Error('Key size is not 128 / 192 / 256');
41
    plaintext = String(plaintext).utf8Encode();
42
    password = String(password).utf8Encode();
43
44
    // use AES itself to encrypt password to get cipher key (using plain password as source for key
45
    // expansion) - gives us well encrypted key (though hashed key might be preferred for prod'n use)
46
    var nBytes = nBits/8;  // no bytes in key (16/24/32)
47
    var pwBytes = new Array(nBytes);
48
    for (var i=0; i<nBytes; i++) {  // use 1st 16/24/32 chars of password for key
49
        pwBytes[i] = i<password.length ?  password.charCodeAt(i) : 0;
50
    }
51
    var key = Aes.cipher(pwBytes, Aes.keyExpansion(pwBytes)); // gives us 16-byte key
52
    key = key.concat(key.slice(0, nBytes-16));  // expand key to 16/24/32 bytes long
53
54
    // initialise 1st 8 bytes of counter block with nonce (NIST SP800-38A §B.2): [0-1] = millisec,
55
    // [2-3] = random, [4-7] = seconds, together giving full sub-millisec uniqueness up to Feb 2106
56
    var counterBlock = new Array(blockSize);
57
58
    var nonce = (new Date()).getTime();  // timestamp: milliseconds since 1-Jan-1970
59
    var nonceMs = nonce%1000;
60
    var nonceSec = Math.floor(nonce/1000);
61
    var nonceRnd = Math.floor(Math.random()*0xffff);
62
    // for debugging: nonce = nonceMs = nonceSec = nonceRnd = 0;
63
64
    for (var i=0; i<2; i++) counterBlock[i]   = (nonceMs  >>> i*8) & 0xff;
65
    for (var i=0; i<2; i++) counterBlock[i+2] = (nonceRnd >>> i*8) & 0xff;
66
    for (var i=0; i<4; i++) counterBlock[i+4] = (nonceSec >>> i*8) & 0xff;
67
68
    // and convert it to a string to go on the front of the ciphertext
69
    var ctrTxt = '';
70
    for (var i=0; i<8; i++) ctrTxt += String.fromCharCode(counterBlock[i]);
71
72
    // generate key schedule - an expansion of the key into distinct Key Rounds for each round
73
    var keySchedule = Aes.keyExpansion(key);
74
75
    var blockCount = Math.ceil(plaintext.length/blockSize);
76
    var ciphertext = '';
77
78
    for (var b=0; b<blockCount; b++) {
79
        // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
80
        // done in two stages for 32-bit ops: using two words allows us to go past 2^32 blocks (68GB)
81
        for (var c=0; c<4; c++) counterBlock[15-c] = (b >>> c*8) & 0xff;
82
        for (var c=0; c<4; c++) counterBlock[15-c-4] = (b/0x100000000 >>> c*8);
83
84
        var cipherCntr = Aes.cipher(counterBlock, keySchedule);  // -- encrypt counter block --
85
86
        // block size is reduced on final block
87
        var blockLength = b<blockCount-1 ? blockSize : (plaintext.length-1)%blockSize+1;
88
        var cipherChar = new Array(blockLength);
89
90
        for (var i=0; i<blockLength; i++) {
91
            // -- xor plaintext with ciphered counter char-by-char --
92
            cipherChar[i] = cipherCntr[i] ^ plaintext.charCodeAt(b*blockSize+i);
93
            cipherChar[i] = String.fromCharCode(cipherChar[i]);
94
        }
95
        ciphertext += cipherChar.join('');
96
97
        // if within web worker, announce progress every 1000 blocks (roughly every 50ms)
98
        if (typeof WorkerGlobalScope != 'undefined' && self instanceof WorkerGlobalScope) {
99
            if (b%1000 == 0) self.postMessage({ progress: b/blockCount });
100
        }
101
    }
102
103
    ciphertext =  (ctrTxt+ciphertext).base64Encode();
104
105
    return ciphertext;
106
};
107
108
109
/**
110
 * Decrypt a text encrypted by AES in counter mode of operation
111
 *
112
 * @param   {string} ciphertext - Cipher text to be decrypted.
113
 * @param   {string} password - Password to use to generate a key for decryption.
114
 * @param   {number} nBits - Number of bits to be used in the key; 128 / 192 / 256.
115
 * @returns {string} Decrypted text
116
 *
117
 * @example
118
 *   var decr = Aes.Ctr.decrypt('lwGl66VVwVObKIr6of8HVqJr', 'pāşšŵōřđ', 256); // 'big secret'
119
 */
120
Aes.Ctr.decrypt = function(ciphertext, password, nBits) {
121
    var blockSize = 16;  // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
122
    if (!(nBits==128 || nBits==192 || nBits==256)) throw new Error('Key size is not 128 / 192 / 256');
123
    ciphertext = String(ciphertext).base64Decode();
124
    password = String(password).utf8Encode();
125
126
    // use AES to encrypt password (mirroring encrypt routine)
127
    var nBytes = nBits/8;  // no bytes in key
128
    var pwBytes = new Array(nBytes);
129
    for (var i=0; i<nBytes; i++) {
130
        pwBytes[i] = i<password.length ?  password.charCodeAt(i) : 0;
131
    }
132
    var key = Aes.cipher(pwBytes, Aes.keyExpansion(pwBytes));
133
    key = key.concat(key.slice(0, nBytes-16));  // expand key to 16/24/32 bytes long
134
135
    // recover nonce from 1st 8 bytes of ciphertext
136
    var counterBlock = new Array(8);
137
    var ctrTxt = ciphertext.slice(0, 8);
138
    for (var i=0; i<8; i++) counterBlock[i] = ctrTxt.charCodeAt(i);
139
140
    // generate key schedule
141
    var keySchedule = Aes.keyExpansion(key);
142
143
    // separate ciphertext into blocks (skipping past initial 8 bytes)
144
    var nBlocks = Math.ceil((ciphertext.length-8) / blockSize);
145
    var ct = new Array(nBlocks);
146
    for (var b=0; b<nBlocks; b++) ct[b] = ciphertext.slice(8+b*blockSize, 8+b*blockSize+blockSize);
147
    ciphertext = ct;  // ciphertext is now array of block-length strings
148
149
    // plaintext will get generated block-by-block into array of block-length strings
150
    var plaintext = '';
151
152
    for (var b=0; b<nBlocks; b++) {
153
        // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
154
        for (var c=0; c<4; c++) counterBlock[15-c] = ((b) >>> c*8) & 0xff;
155
        for (var c=0; c<4; c++) counterBlock[15-c-4] = (((b+1)/0x100000000-1) >>> c*8) & 0xff;
156
157
        var cipherCntr = Aes.cipher(counterBlock, keySchedule);  // encrypt counter block
158
159
        var plaintxtByte = new Array(ciphertext[b].length);
160
        for (var i=0; i<ciphertext[b].length; i++) {
161
            // -- xor plaintext with ciphered counter byte-by-byte --
162
            plaintxtByte[i] = cipherCntr[i] ^ ciphertext[b].charCodeAt(i);
163
            plaintxtByte[i] = String.fromCharCode(plaintxtByte[i]);
164
        }
165
        plaintext += plaintxtByte.join('');
166
167
        // if within web worker, announce progress every 1000 blocks (roughly every 50ms)
168
        if (typeof WorkerGlobalScope != 'undefined' && self instanceof WorkerGlobalScope) {
169
            if (b%1000 == 0) self.postMessage({ progress: b/nBlocks });
170
        }
171
    }
172
173
    plaintext = plaintext.utf8Decode();  // decode from UTF8 back to Unicode multi-byte chars
174
175
    return plaintext;
176
};
177
178
179
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
180
181
/* Extend String object with method to encode multi-byte string to utf8
182
 * - monsur.hossa.in/2012/07/20/utf-8-in-javascript.html
183
 * - note utf8Encode is an identity function with 7-bit ascii strings, but not with 8-bit strings;
184
 * - utf8Encode('x') = 'x', but utf8Encode('ça') = 'ça', and utf8Encode('ça') = 'ça'*/
185
if (typeof String.prototype.utf8Encode == 'undefined') {
186
    String.prototype.utf8Encode = function() {
187
        return unescape( encodeURIComponent( this ) );
188
    };
189
}
190
191
/* Extend String object with method to decode utf8 string to multi-byte */
192
if (typeof String.prototype.utf8Decode == 'undefined') {
193
    String.prototype.utf8Decode = function() {
194
        try {
195
            return decodeURIComponent( escape( this ) );
196
        } catch (e) {
197
            return this; // invalid UTF-8? return as-is
198
        }
199
    };
200
}
201
202
/* Extend String object with method to encode base64
203
 * - developer.mozilla.org/en-US/docs/Web/API/window.btoa, nodejs.org/api/buffer.html
204
 * - note: btoa & Buffer/binary work on single-byte Unicode (C0/C1), so ok for utf8 strings, not for general Unicode...
205
 * - note: if btoa()/atob() are not available (eg IE9-), try github.com/davidchambers/Base64.js */
206
if (typeof String.prototype.base64Encode == 'undefined') {
207
    String.prototype.base64Encode = function() {
208
        if (typeof btoa != 'undefined') return btoa(this); // browser
209
        if (typeof Buffer != 'undefined') return new Buffer(this, 'binary').toString('base64'); // Node.js
210
        throw new Error('No Base64 Encode');
211
    };
212
}
213
214
/* Extend String object with method to decode base64 */
215
if (typeof String.prototype.base64Decode == 'undefined') {
216
    String.prototype.base64Decode = function() {
217
        if (typeof atob != 'undefined') return atob(this); // browser
218
        if (typeof Buffer != 'undefined') return new Buffer(this, 'base64').toString('binary'); // Node.js
219
        throw new Error('No Base64 Decode');
220
    };
221
}
222
223
224
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
225
if (typeof module != 'undefined' && module.exports) module.exports = Aes.Ctr; // ≡ export default Aes.Ctr
226